#include <Master.hpp>
#include <iostream>
#include <Trie.hpp>
#include <sstream>
#include <Utility.hpp>
#include <cstdlib>
#include <ctime>
#include <GTrie.hpp>
#include <mpi.h>
#include <list>
#include <vector>

Master::Master(std::istream& source, int np, int ext) {
	this->source = &source;
	this->np 	 = np;
	this->ext    = ext;
}

void Master::run() {

	// Gera timestamp (apenas mestre faz isso)
	int vtime = time(0);

	// Todos recebem o timestamp atual
	MPI::COMM_WORLD.Bcast(&vtime, 1, MPI_INT, 0);

	// Configura seed unica para random
	srand(vtime);

	// Cria trie
	Trie * trie = new Trie();

	// Le as palavras do arquivo serializado
	int qtyWords = trie->deserialize(*source);

	std::cerr << "There are " << qtyWords << 
		" distinct words\n";

	std::vector<std::string> words;

	// Itemiza
	// (Veja comentarios na declaracao do metodo)
	trie->itemize(words);

	std::vector<int> remaining(words.size());

	// Deleta trie de palavras
	delete trie;

	// Recria Trie. Dessa vez, ela contera partes de palavras
	trie = new Trie();

	// Cria gtrie que associara partes a palavras inteiras 
	GTrie<int>  * gtrie = new GTrie<int>();

	// Efetua split (veja comentarios na declaracao do metodo)
	Utility::split(trie, gtrie, words, remaining);


	int qtyParts;

	// Envia trie serializada para escravos
	{
		std::stringstream ss;
		trie->serialize(ss);
		std::string str;
		ss >> str;
		int sz = str.size();

		char * buffer = new char[sz+1];

		buffer[sz] = 0;
		for (int i = 0; i < sz; i++)
			buffer[i] = str[i];

		MPI::COMM_WORLD.Bcast(&sz, 1, MPI_INT, 0);
		MPI::COMM_WORLD.Bcast(buffer, sz, MPI_CHAR, 0);

		std::stringstream ss2;
		ss2 << std::string(buffer);

		delete buffer;

		qtyParts = trie->deserialize(ss2);
	}

	std::vector<std::string> parts;

	trie->itemize(parts);

	std::cerr << "There are " << qtyParts << 
		" distinct parts\n";


	int myqty = qtyParts;

	std::cerr << "Master received "
		<< myqty << " parts\n";

	int tryc = 0;
	Trie * foundTrie = new Trie();

	
	double nextTarget = 0.1;
	int foundW = 0;

	// Buffer para geracao de palavras aleatorias
	char wbuffer[6];

	// Ponteiro para efetuar remocoes na gtrie
	GTrie<int> * rpointer;

	long long tryT = 0;

	// Enquanto faltarem partes a serem encontradas e comunicacao
	// nao for devidamente encerrada
	for (int found = 0;; ) {

		// Se chegou a quantidade de tentativas limite antes de troca
		// de dados ou se nao ha mais partes para serem encontradas,
		// porem comunicacao nao foi encerrada
		if (tryc == ext || found >= myqty) {

			bool again = false;

			// zera quantidade de tentativas
			tryc = 0;

			std::stringstream ss;
			int status;

			// Recebe, de cada processo, partes encontradas e as deserializa
			// em uma unica Trie, que contera todas as partes encontradas
			// (deserializacao e' um processo incremental)
			for (int i = 1; i < np; i++) {

				MPI::COMM_WORLD.Recv(&status, 1, MPI_INT, i, MPI_ANY_TAG);
				if (status) again = true;
				int bsz;
				MPI::COMM_WORLD.Recv(&bsz, 1, MPI_INT, i, MPI_ANY_TAG);

				char * buffer = new char[bsz+1];
				buffer[bsz] = 0;

				MPI::Status status;
				for (int st = 0; st < bsz; ) {
					MPI::COMM_WORLD.Recv(buffer, bsz,
						MPI_CHAR, i, MPI_ANY_TAG, status);
					st += status.Get_count(MPI_CHAR);
				}

				ss << std::string(buffer);
				delete buffer;
				foundTrie->deserialize(ss);
			}

			foundTrie->serialize(ss);
			std::string str;
			ss >> str;
			int sz = str.size();

			char * buffer = new char[sz+1];
			buffer[sz] = 0;
			for (int i = 0; i < sz; i++)
				buffer[i] = str[i];


			// Envia trie serializada com partes encontradas
			// por todos os nos, para todos os nos
			MPI::COMM_WORLD.Bcast(&sz, 1, MPI_INT, 0);
			MPI::COMM_WORLD.Bcast(buffer, sz, MPI_CHAR, 0);
			delete buffer;

			std::list<std::string> list;
			foundTrie->itemize(list);
			std::list<std::string>::iterator it, itEnd;
			it = list.begin();
			itEnd = list.end();

			// Marca como encontrado as partes que os outros encontraram
			int helped = 0;
			for (; it != itEnd; it++) {
				Trie * search = 
					trie->retrieve(it->c_str());
				if (search) {
					helped++;
					found++;
					search->remove();


					std::vector<int> connections = 
						gtrie->retrieve(it->c_str(), &rpointer);

					// Efetua a associacao parte->palavra para verificar palavras
					// completas que ja foram encontradas
					for(int i = 0; i < connections.size(); i++) {
						remaining[connections[i]]--;
						if (!remaining[connections[i]]) {
							foundW ++;

							while (((double)foundW) >= ((double)qtyWords) * nextTarget) {

								std::cerr << 
									(nextTarget * 100.0) 
									<< " percent reached at " << 
									(clock()/1000000.0) << std::endl;
								nextTarget += 0.1;
							}

							std::cout << words[connections[i]] <<
								std::endl;
						}
					}

					rpointer->remove();

				}
			}
			if (helped)
			std::cerr << "Master helped " << helped <<
				 " - " << found << "\n";

			delete foundTrie;
			foundTrie = new Trie();

			status = 0;
			if (again || found < myqty) status = 1;

			MPI::COMM_WORLD.Bcast(&status, 1, MPI_INT, 0);

			if (!status) break;
			else if (found >= myqty) continue;
		}

		// Gera parte aleatoria
		Utility::randomWord(wbuffer);
		tryT++;

		// Tenta encontra-la
		Trie * search = 
			trie->retrieveUntil(wbuffer);

		// Incrementa contador de tentativas
		tryc ++;

		// Caso tenha sido encontrada
		if (search) {
			// Incrementa contador de encontradas
			found++;
			// Remove da trie
			search->remove();
			// Insere na trie de partes encontradas
			foundTrie->insert(wbuffer);

			std::vector<int> connections = 
				gtrie->retrieve(wbuffer, &rpointer);

			// Efetua a associacao parte->palavra para verificar palavras
			// completas que ja foram encontradas
			for(int i = 0; i < connections.size(); i++) {
				remaining[connections[i]]--;
				if (!remaining[connections[i]]) {
					foundW ++;

					while (((double)foundW) >= ((double)qtyWords) * nextTarget) {
						std::cerr << 
							(nextTarget * 100.0) 
							<< " percent reached at " << 
							(clock()/1000000.0) << std::endl;
						nextTarget += 0.1;
					}

					std::cout << words[connections[i]] <<
						std::endl;
				}
			}
			
			// Remove da gtrie de associacoes
			rpointer->remove();
		}
	}

	delete trie;
	delete foundTrie;

	std::cerr << tryT << std::endl;
	std::cerr << "Master finished. Waiting for others\n";

	MPI::COMM_WORLD.Barrier();

	std::cerr << "Very good!\n";

}
